#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <signal.h>
#include <pthread.h>
#include <time.h>

#include "../include/icam.h"

SOCKET reg_skt;
SOCKET viewer_skt;

t_icam_reg_infor icam_info[ICAM_MAX_NODE_NUM];

INT32S socket_bind(SOCKET skt, struct sockaddr_in *addr)
{
    INT32S ret;

    ret = bind(skt, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
    if (ret != 0) {
        printf("bind error\n");
    }

    return ret;
}

INT32S socket_connect(SOCKET skt, struct sockaddr_in *addr)
{
    INT32S ret;

    ret = connect(skt, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
    if (ret != 0) {
        printf("connect error\n");
    }

    return ret;
}

INT32S icam_cmd_update_reg_infor(INT32U id, struct sockaddr_in *icam_addr)
{
    if (id >= ICAM_MAX_NODE_NUM) {
        debug_printf("beacon id error\n");
        return -1;
    }

    icam_info[id].log_time = time(NULL);
    icam_info[id].sin_port = icam_addr->sin_port;
    icam_info[id].s_addr = icam_addr->sin_addr.s_addr;
    /*
    debug_printf("iCam[%d] from: %s:%d\n", 
            id,
            inet_ntoa(*((struct in_addr *) &(icam_info[id].s_addr))), 
            ntohs(icam_info[id].sin_port));
    debug_printf("iCam[%d] log time is: %s\n", id, ctime(&icam_info[id].log_time));
    */

    return 0;
}

INT32S icam_cmd_find_icam(INT32U id, struct sockaddr_in *viewer_addr)
{
    t_icam_cmd icam_cmd;
    time_t cur_time = time(NULL);
    struct sockaddr_in icam_addr;

    if (id >= ICAM_MAX_NODE_NUM) {
        debug_printf("iCam id error\n");
        memcpy(icam_cmd.magic, ICAM_CMD_MAGIC, 4);
        icam_cmd.cmd_id = ICAM_CMD_ICAM_NOT_LOGON;
        icam_cmd.payload_length = 0;
        sendto(viewer_skt, &icam_cmd, ICAM_MAX_CMD_HEAD_SIZE + icam_cmd.payload_length, 0, (struct sockaddr *)viewer_addr, sizeof(struct sockaddr_in));
        return -1;
    }

    if ((cur_time - icam_info[id].log_time) > 3) { // iCam has not logged in
        debug_printf("iCam not logon\n");
        memcpy(icam_cmd.magic, ICAM_CMD_MAGIC, 4);
        icam_cmd.cmd_id = ICAM_CMD_ICAM_NOT_LOGON;
        icam_cmd.payload_length = 0;
        sendto(viewer_skt, &icam_cmd, ICAM_MAX_CMD_HEAD_SIZE + icam_cmd.payload_length, 0, (struct sockaddr *)viewer_addr, sizeof(struct sockaddr_in));
    } else {
        debug_printf("iCam found\n");
        icam_addr.sin_family = AF_INET;
        icam_addr.sin_port = icam_info[id].sin_port;
        icam_addr.sin_addr.s_addr = icam_info[id].s_addr;

        memcpy(icam_cmd.magic, ICAM_CMD_MAGIC, 4);
        icam_cmd.cmd_id = ICAM_CMD_VIEWER_INFO;
        icam_cmd.payload_length = 6;
        *(INT32U *)(icam_cmd.buf) = viewer_addr->sin_addr.s_addr;
        *(INT16U *)(icam_cmd.buf + 4) = viewer_addr->sin_port;
        sendto(reg_skt, &icam_cmd, ICAM_MAX_CMD_HEAD_SIZE + icam_cmd.payload_length, 0, (struct sockaddr *) &icam_addr, sizeof(struct sockaddr_in));

        icam_cmd.cmd_id = ICAM_CMD_ICAM_LOG_INFO;
        icam_cmd.payload_length = 6;
        *(INT32U *)(icam_cmd.buf) = icam_info[id].s_addr;
        *(INT16U *)(icam_cmd.buf + 4) = icam_info[id].sin_port;
        sendto(viewer_skt, &icam_cmd, ICAM_MAX_CMD_HEAD_SIZE + icam_cmd.payload_length, 0, (struct sockaddr *)viewer_addr, sizeof(struct sockaddr_in));
    }

    return 0;
}

INT32S icam_cmd_parser(t_icam_cmd *cmd, struct sockaddr_in *addr)
{
    INT32S ret;

    if (strncmp(cmd->magic, ICAM_CMD_MAGIC, 4) != 0) {
        debug_printf("icam_cmd magic error: %c%c%c%c\n", cmd->magic[0], cmd->magic[1], cmd->magic[2], cmd->magic[3]);
        return -1;
    }

    //debug_printf("cmd id = %d\n", cmd->cmd_id);

    switch (cmd->cmd_id) {
        case ICAM_CMD_UPDATE_BEACON :
            // beacon updated from iCam
            ret = icam_cmd_update_reg_infor(*(INT32U *)cmd->buf, addr);
            break;
        case ICAM_CMD_FIND_ICAM :
            // a request from a viewer, he needs to connect to iCam
            ret = icam_cmd_find_icam(*(INT32U *)cmd->buf, addr);
            break;
        default :
            break;
    }

    return ret;
}

void icam_reg_task(void)
{
    struct sockaddr_in local_addr;
    struct sockaddr_in icam_addr;
    INT32U icam_addr_len = sizeof(icam_addr);
    INT32S ret;
    t_icam_cmd icam_cmd;

    reg_skt = socket(AF_INET, SOCK_DGRAM, 0);

    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(ICAM_NET_REGISTRY_PORT);
    local_addr.sin_addr.s_addr = htons(INADDR_ANY);
    
    if (socket_bind(reg_skt, &local_addr) != 0) {
        ///< TODO
        while (1) ;
    }

    while (1) {
        ///< waiting for iCam's beacon
        ret = recvfrom(reg_skt, &icam_cmd, ICAM_MAX_CMD_SIZE, 0, (struct sockaddr *) &icam_addr, &icam_addr_len);
        if (ret == -1) {
            debug_printf("recv cmd error\n");
        } else {
            icam_cmd_parser(&icam_cmd, &icam_addr);
        }
    }
}

void icam_viewer_task(void)
{
    struct sockaddr_in local_addr;
    struct sockaddr_in viewer_addr;
    INT32U viewer_addr_len = sizeof(viewer_addr);
    INT32S ret;
    t_icam_cmd viewer_cmd;

    viewer_skt = socket(AF_INET, SOCK_DGRAM, 0);

    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(VIEWER_NET_REGISTRY_PORT);
    local_addr.sin_addr.s_addr = htons(INADDR_ANY);
    
    if (socket_bind(viewer_skt, &local_addr) != 0) {
        ///< TODO
        while (1) ;
    }

    while (1) {
        ///< waiting for viewer's request
        ret = recvfrom(viewer_skt, &viewer_cmd, ICAM_MAX_CMD_SIZE, 0, (struct sockaddr *) &viewer_addr, &viewer_addr_len);
        if (ret == -1) {
            debug_printf("recv cmd error\n");
        } else {
            icam_cmd_parser(&viewer_cmd, &viewer_addr);
        }
    }
}

INT32S icam_reg_task_create(void)
{
    pthread_t id;
    int ret;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    ret = pthread_create(&id, &attr, (void *)icam_reg_task, NULL);
    if (ret != 0) {
        debug_printf("net_cmd_thread create faild\n");
    }
    
    return ret;
}

INT32S icam_viewer_task_create(void)
{
    pthread_t id;
    int ret;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    ret = pthread_create(&id, &attr, (void *)icam_viewer_task, NULL);
    if (ret != 0) {
        debug_printf("net_cmd_thread create faild\n");
    }
    
    return ret;
}

void server_init(void)
{
    int i;
    
    for (i=0; i<ICAM_MAX_NODE_NUM; i++) {
        icam_info[i].log_time = 0;
    }
}

int main(int argc, char *argv[])
{
    server_init();

    icam_reg_task_create();
    icam_viewer_task_create();

    while (1) {
        ;
    }

    return 0;
}
